package info.openrocket.core.file.openrocket.importt;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Locale;

import info.openrocket.core.material.Material;
import info.openrocket.core.preset.ComponentPreset;
import info.openrocket.core.rocketcomponent.AxialStage;
import info.openrocket.core.rocketcomponent.BodyComponent;
import info.openrocket.core.rocketcomponent.BodyTube;
import info.openrocket.core.rocketcomponent.Bulkhead;
import info.openrocket.core.rocketcomponent.CenteringRing;
import info.openrocket.core.rocketcomponent.DeploymentConfiguration;
import info.openrocket.core.rocketcomponent.DeploymentConfiguration.DeployEvent;
import info.openrocket.core.rocketcomponent.DesignType;
import info.openrocket.core.rocketcomponent.EllipticalFinSet;
import info.openrocket.core.rocketcomponent.EngineBlock;
import info.openrocket.core.rocketcomponent.ExternalComponent;
import info.openrocket.core.rocketcomponent.ExternalComponent.Finish;
import info.openrocket.core.rocketcomponent.FinSet;
import info.openrocket.core.rocketcomponent.FreeformFinSet;
import info.openrocket.core.rocketcomponent.InnerTube;
import info.openrocket.core.rocketcomponent.LaunchLug;
import info.openrocket.core.rocketcomponent.MassComponent;
import info.openrocket.core.rocketcomponent.MassObject;
import info.openrocket.core.rocketcomponent.NoseCone;
import info.openrocket.core.rocketcomponent.Parachute;
import info.openrocket.core.rocketcomponent.ParallelStage;
import info.openrocket.core.rocketcomponent.PodSet;
import info.openrocket.core.rocketcomponent.RadiusRingComponent;
import info.openrocket.core.rocketcomponent.RailButton;
import info.openrocket.core.rocketcomponent.RecoveryDevice;
import info.openrocket.core.rocketcomponent.ReferenceType;
import info.openrocket.core.rocketcomponent.RingComponent;
import info.openrocket.core.rocketcomponent.Rocket;
import info.openrocket.core.rocketcomponent.RocketComponent;
import info.openrocket.core.rocketcomponent.ShockCord;
import info.openrocket.core.rocketcomponent.StageSeparationConfiguration;
import info.openrocket.core.rocketcomponent.Streamer;
import info.openrocket.core.rocketcomponent.StructuralComponent;
import info.openrocket.core.rocketcomponent.SymmetricComponent;
import info.openrocket.core.rocketcomponent.ThicknessRingComponent;
import info.openrocket.core.rocketcomponent.Transition;
import info.openrocket.core.rocketcomponent.TrapezoidFinSet;
import info.openrocket.core.rocketcomponent.TubeCoupler;
import info.openrocket.core.rocketcomponent.TubeFinSet;
import info.openrocket.core.util.BugException;
import info.openrocket.core.util.ORColor;
import info.openrocket.core.util.LineStyle;
import info.openrocket.core.util.Reflection;

class DocumentConfig {
	
	/* Remember to update OpenRocketSaver as well! */
	public static final String[] SUPPORTED_VERSIONS = { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8",
			"1.9", "1.10" , "1.11"};
	
	/**
	 * Divisor used in converting an integer version to the point-represented version.
	 * The integer version divided by this value is the major version and the remainder is
	 * the minor version.  For example 101 corresponds to file version "1.1".
	 */
	public static final int FILE_VERSION_DIVISOR = 100;
	
	
	////////  Component constructors
	static final HashMap<String, Constructor<? extends RocketComponent>> constructors = new HashMap<>();
	static {
		try {
			// External components
			constructors.put("bodytube", BodyTube.class.getConstructor());
			constructors.put("transition", Transition.class.getConstructor());
			constructors.put("nosecone", NoseCone.class.getConstructor());
			constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor());
			constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor());
			constructors.put("freeformfinset", FreeformFinSet.class.getConstructor());
			constructors.put("tubefinset", TubeFinSet.class.getConstructor());
			constructors.put("launchlug", LaunchLug.class.getConstructor());
			constructors.put("railbutton", RailButton.class.getConstructor());
			
			// Internal components
			constructors.put("engineblock", EngineBlock.class.getConstructor());
			constructors.put("innertube", InnerTube.class.getConstructor());
			constructors.put("tubecoupler", TubeCoupler.class.getConstructor());
			constructors.put("bulkhead", Bulkhead.class.getConstructor());
			constructors.put("centeringring", CenteringRing.class.getConstructor());
			
			constructors.put("masscomponent", MassComponent.class.getConstructor());
			constructors.put("shockcord", ShockCord.class.getConstructor());
			constructors.put("parachute", Parachute.class.getConstructor());
			constructors.put("streamer", Streamer.class.getConstructor());
			
			// Other
			constructors.put("stage", AxialStage.class.getConstructor());
			constructors.put("boosterset", ParallelStage.class.getConstructor());
			constructors.put("parallelstage", ParallelStage.class.getConstructor());
			constructors.put("podset", PodSet.class.getConstructor());
			
		} catch (NoSuchMethodException e) {
			throw new BugException(
					"Error in constructing the 'constructors' HashMap.");
		}
	}
	
	
	////////  Parameter setters
	/*
	 * The keys are of the form Class:param, where Class is the class name and param
	 * the element name.  Setters are searched for in descending class order.
	 * A setter of null means setting the parameter is not allowed.
	 */
	static final HashMap<String, Setter> setters = new HashMap<>();
	static {
		// RocketComponent
		setters.put("RocketComponent:name", new StringSetter(
				Reflection.findMethod(RocketComponent.class, "setName", String.class)));
		setters.put("RocketComponent:id", new StringSetter(
				Reflection.findMethod(RocketComponent.class, "setID", String.class)));
		setters.put("RocketComponent:color", new ColorSetter(
				Reflection.findMethod(RocketComponent.class, "setColor", ORColor.class)));
		setters.put("RocketComponent:linestyle", new EnumSetter<>(
				Reflection.findMethod(RocketComponent.class, "setLineStyle", LineStyle.class),
				LineStyle.class));
		setters.put("RocketComponent:position", new AxialPositionSetter());
		setters.put("RocketComponent:axialoffset", new AxialPositionSetter());
		setters.put("RocketComponent:overridemass", new OverrideSetter(
				Reflection.findMethod(RocketComponent.class, "setOverrideMass", double.class),
				Reflection.findMethod(RocketComponent.class, "setMassOverridden", boolean.class)));
		setters.put("RocketComponent:overridecg", new OverrideSetter(
				Reflection.findMethod(RocketComponent.class, "setOverrideCGX", double.class),
				Reflection.findMethod(RocketComponent.class, "setCGOverridden", boolean.class)));
		setters.put("RocketComponent:overridecd", new OverrideSetter(
				Reflection.findMethod(RocketComponent.class, "setOverrideCD", double.class),
				Reflection.findMethod(RocketComponent.class, "setCDOverridden", boolean.class)));
		// TODO: delete overridesubcomponents setters if no compatibility is needed
		// anymore with OR 15.03
		setters.put("RocketComponent:overridesubcomponents", new BooleanSetter(
				Reflection.findMethod(RocketComponent.class, "setSubcomponentsOverridden", boolean.class)));

		setters.put("RocketComponent:overridesubcomponentsmass", new BooleanSetter(
				Reflection.findMethod(RocketComponent.class, "setSubcomponentsOverriddenMass", boolean.class)));
		setters.put("RocketComponent:overridesubcomponentscg", new BooleanSetter(
				Reflection.findMethod(RocketComponent.class, "setSubcomponentsOverriddenCG", boolean.class)));
		setters.put("RocketComponent:overridesubcomponentscd", new BooleanSetter(
				Reflection.findMethod(RocketComponent.class, "setSubcomponentsOverriddenCD", boolean.class)));
		setters.put("RocketComponent:comment", new StringSetter(
				Reflection.findMethod(RocketComponent.class, "setComment", String.class)));
		setters.put("RocketComponent:preset", new ComponentPresetSetter(
				Reflection.findMethod(RocketComponent.class, "loadPreset", ComponentPreset.class)));
		

		// ExternalComponent
		setters.put("ExternalComponent:finish", new EnumSetter<>(
				Reflection.findMethod(ExternalComponent.class, "setFinish", Finish.class),
				Finish.class));
		setters.put("ExternalComponent:material", new MaterialSetter(
				Reflection.findMethod(ExternalComponent.class, "setMaterial", Material.class),
				Material.Type.BULK));
		
		// BodyComponent
		setters.put("BodyComponent:length", new DoubleSetter(
				Reflection.findMethod(BodyComponent.class, "setLength", double.class)));

		// BodyTube
		setters.put("BodyTube:radius", new DoubleSetter(
				Reflection.findMethod(BodyTube.class, "setOuterRadius", double.class),
				"auto", " ",
				Reflection.findMethod(BodyTube.class, "setOuterRadiusAutomatic", boolean.class)));

		// Parallel Stage
		setters.put("ParallelStage:instancecount", new IntSetter(
				Reflection.findMethod(ParallelStage.class, "setInstanceCount", int.class)));
		setters.put("ParallelStage:angleoffset", new AnglePositionSetter());
		setters.put("ParallelStage:radiusoffset", new RadiusPositionSetter());

		// SymmetricComponent
		setters.put("SymmetricComponent:thickness", new DoubleSetter(
				Reflection.findMethod(SymmetricComponent.class, "setThickness", double.class, boolean.class),
				"filled",
				Reflection.findMethod(SymmetricComponent.class, "setFilled", boolean.class),
				false));
		
		// LaunchLug
		setters.put("LaunchLug:instancecount", new IntSetter(
				Reflection.findMethod(LaunchLug.class, "setInstanceCount", int.class)));
		setters.put("LaunchLug:instanceseparation", new DoubleSetter(
				Reflection.findMethod(LaunchLug.class, "setInstanceSeparation", double.class)));
		setters.put("LaunchLug:radialdirection", new DoubleSetter(
				Reflection.findMethod(LaunchLug.class, "setAngleOffset", double.class), Math.PI / 180.0));
		setters.put("LaunchLug:angleoffset", new AnglePositionSetter());
		setters.put("LaunchLug:radius", new DoubleSetter(
				Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
		setters.put("LaunchLug:length", new DoubleSetter(
				Reflection.findMethod(LaunchLug.class, "setLength", double.class)));
		setters.put("LaunchLug:thickness", new DoubleSetter(
				Reflection.findMethod(LaunchLug.class, "setThickness", double.class)));

		// RailButton
		setters.put("RailButton:instancecount", new IntSetter(
				Reflection.findMethod(RailButton.class, "setInstanceCount", int.class)));
		setters.put("RailButton:instanceseparation", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setInstanceSeparation", double.class)));
		setters.put("RailButton:angleoffset", new AnglePositionSetter());
		setters.put("RailButton:height", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setTotalHeight", double.class)));
		setters.put("RailButton:baseheight", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setBaseHeight", double.class)));
		setters.put("RailButton:flangeheight", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setFlangeHeight", double.class)));
		setters.put("RailButton:screwheight", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setScrewHeight", double.class)));
		setters.put("RailButton:outerdiameter", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setOuterDiameter", double.class)));
		setters.put("RailButton:innerdiameter", new DoubleSetter(
				Reflection.findMethod(RailButton.class, "setInnerDiameter", double.class)));

			
		// Transition
		setters.put("Transition:shape", new EnumSetter<>(
				Reflection.findMethod(Transition.class, "setShapeType", Transition.Shape.class),
				Transition.Shape.class));
		setters.put("Transition:shapeclipped", new BooleanSetter(
				Reflection.findMethod(Transition.class, "setClipped", boolean.class)));
		setters.put("Transition:shapeparameter", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setShapeParameter", double.class)));
		
		setters.put("Transition:foreradius", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setForeRadius", double.class, boolean.class),
				"auto", " ",
				Reflection.findMethod(Transition.class, "setForeRadiusAutomatic", boolean.class),
				false));
		setters.put("Transition:aftradius", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setAftRadius", double.class, boolean.class),
				"auto", " ",
				Reflection.findMethod(Transition.class, "setAftRadiusAutomatic", boolean.class),
				false));
		
		setters.put("Transition:foreshoulderradius", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setForeShoulderRadius", double.class, boolean.class),
				null, null, false));
		setters.put("Transition:foreshoulderlength", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setForeShoulderLength", double.class)));
		setters.put("Transition:foreshoulderthickness", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setForeShoulderThickness", double.class)));
		setters.put("Transition:foreshouldercapped", new BooleanSetter(
				Reflection.findMethod(Transition.class, "setForeShoulderCapped", boolean.class)));
		
		setters.put("Transition:aftshoulderradius", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setAftShoulderRadius", double.class, boolean.class),
				null, null, false));
		setters.put("Transition:aftshoulderlength", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setAftShoulderLength", double.class)));
		setters.put("Transition:aftshoulderthickness", new DoubleSetter(
				Reflection.findMethod(Transition.class, "setAftShoulderThickness", double.class)));
		setters.put("Transition:aftshouldercapped", new BooleanSetter(
				Reflection.findMethod(Transition.class, "setAftShoulderCapped", boolean.class)));

		setters.put("NoseCone:isflipped", new BooleanSetter(
				Reflection.findMethod(NoseCone.class, "setFlipped", boolean.class, boolean.class), false));
		// NoseCone - disable disallowed elements
		setters.put("NoseCone:foreradius", null);
		setters.put("NoseCone:foreshoulderradius", null);
		setters.put("NoseCone:foreshoulderlength", null);
		setters.put("NoseCone:foreshoulderthickness", null);
		setters.put("NoseCone:foreshouldercapped", null);
		
		// FinSet
		setters.put("FinSet:fincount", new IntSetter(
				Reflection.findMethod(FinSet.class, "setFinCount", int.class)));
		setters.put("FinSet:instancecount", new IntSetter(
				Reflection.findMethod(FinSet.class, "setInstanceCount", int.class)));
		setters.put("FinSet:rotation", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
		setters.put("FinSet:angleoffset", new AnglePositionSetter());
		setters.put("FinSet:radiusoffset", new RadiusPositionSetter());
		setters.put("FinSet:thickness", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setThickness", double.class)));
		setters.put("FinSet:crosssection", new EnumSetter<>(
				Reflection.findMethod(FinSet.class, "setCrossSection", FinSet.CrossSection.class),
				FinSet.CrossSection.class));
		setters.put("FinSet:cant", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setCantAngle", double.class), Math.PI / 180.0));
		setters.put("FinSet:tabheight", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setTabHeight", double.class)));
		setters.put("FinSet:tablength", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setTabLength", double.class)));
		setters.put("FinSet:tabposition", new FinTabPositionSetter());
		setters.put("FinSet:filletradius", new DoubleSetter(
				Reflection.findMethod(FinSet.class, "setFilletRadius", double.class)));
		setters.put("FinSet:filletmaterial", new MaterialSetter(
				Reflection.findMethod(FinSet.class, "setFilletMaterial", Material.class),
				Material.Type.BULK));
		
		// TrapezoidFinSet
		setters.put("TrapezoidFinSet:rootchord", new DoubleSetter(
				Reflection.findMethod(TrapezoidFinSet.class, "setRootChord", double.class)));
		setters.put("TrapezoidFinSet:tipchord", new DoubleSetter(
				Reflection.findMethod(TrapezoidFinSet.class, "setTipChord", double.class)));
		setters.put("TrapezoidFinSet:sweeplength", new DoubleSetter(
				Reflection.findMethod(TrapezoidFinSet.class, "setSweep", double.class)));
		setters.put("TrapezoidFinSet:height", new DoubleSetter(
				Reflection.findMethod(TrapezoidFinSet.class, "setHeight", double.class)));
		
		// EllipticalFinSet
		setters.put("EllipticalFinSet:rootchord", new DoubleSetter(
				Reflection.findMethod(EllipticalFinSet.class, "setLength", double.class)));
		setters.put("EllipticalFinSet:height", new DoubleSetter(
				Reflection.findMethod(EllipticalFinSet.class, "setHeight", double.class)));
		
		// FreeformFinSet points handled as a special handler
		
		// TubeFinSet
		setters.put("TubeFinSet:fincount", new IntSetter(
				Reflection.findMethod(TubeFinSet.class, "setFinCount", int.class)));
		setters.put("TubeFinSet:rotation", new DoubleSetter(
				Reflection.findMethod(TubeFinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
		setters.put("TubeFinSet:thickness", new DoubleSetter(
				Reflection.findMethod(TubeFinSet.class, "setThickness", double.class)));
		setters.put("TubeFinSet:length", new DoubleSetter(
				Reflection.findMethod(TubeFinSet.class, "setLength", double.class)));
		setters.put("TubeFinSet:radius", new DoubleSetter(
				Reflection.findMethod(TubeFinSet.class, "setOuterRadius", double.class),
				"auto",
				Reflection.findMethod(TubeFinSet.class, "setOuterRadiusAutomatic", boolean.class)));
		setters.put("TubeFinSet:instancecount", new IntSetter(
				Reflection.findMethod(TubeFinSet.class, "setInstanceCount", int.class)));
		setters.put("TubeFinSet:angleoffset", new AnglePositionSetter());
		setters.put("TubeFinSet:radiusoffset", new RadiusPositionSetter());

		// InternalComponent - nothing
		
		// StructuralComponent
		setters.put("StructuralComponent:material", new MaterialSetter(
				Reflection.findMethod(StructuralComponent.class, "setMaterial", Material.class),
				Material.Type.BULK));
		
		// RingComponent
		setters.put("RingComponent:length", new DoubleSetter(
				Reflection.findMethod(RingComponent.class, "setLength", double.class)));
		setters.put("RingComponent:radialposition", new DoubleSetter(
				Reflection.findMethod(RingComponent.class, "setRadialPosition", double.class)));
		setters.put("RingComponent:radialdirection", new DoubleSetter(
				Reflection.findMethod(RingComponent.class, "setRadialDirection", double.class),
				Math.PI / 180.0));

		// ThicknessRingComponent - radius on separate components due to differing
		// automatics
		setters.put("ThicknessRingComponent:thickness", new DoubleSetter(
				Reflection.findMethod(ThicknessRingComponent.class, "setThickness", double.class)));
		
		// EngineBlock
		setters.put("EngineBlock:outerradius", new DoubleSetter(
				Reflection.findMethod(EngineBlock.class, "setOuterRadius", double.class),
				"auto",
				Reflection.findMethod(EngineBlock.class, "setOuterRadiusAutomatic", boolean.class)));
		
		// TubeCoupler
		setters.put("TubeCoupler:outerradius", new DoubleSetter(
				Reflection.findMethod(TubeCoupler.class, "setOuterRadius", double.class),
				"auto",
				Reflection.findMethod(TubeCoupler.class, "setOuterRadiusAutomatic", boolean.class)));
		
		// InnerTube
		setters.put("InnerTube:outerradius", new DoubleSetter(
				Reflection.findMethod(InnerTube.class, "setOuterRadius", double.class)));
		setters.put("InnerTube:clusterconfiguration", new ClusterConfigurationSetter());
		setters.put("InnerTube:clusterscale", new DoubleSetter(
				Reflection.findMethod(InnerTube.class, "setClusterScale", double.class)));
		setters.put("InnerTube:clusterrotation", new DoubleSetter(
				Reflection.findMethod(InnerTube.class, "setClusterRotation", double.class),
				Math.PI / 180.0));
		
		// RadiusRingComponent
		setters.put("RadiusRingComponent:instancecount", new IntSetter(
				Reflection.findMethod(RadiusRingComponent.class, "setInstanceCount", int.class)));
		setters.put("RadiusRingComponent:instanceseparation", new DoubleSetter(
				Reflection.findMethod(RadiusRingComponent.class, "setInstanceSeparation", double.class)));

		// Bulkhead
		setters.put("RadiusRingComponent:innerradius", new DoubleSetter(
				Reflection.findMethod(RadiusRingComponent.class, "setInnerRadius", double.class)));
		setters.put("Bulkhead:outerradius", new DoubleSetter(
				Reflection.findMethod(Bulkhead.class, "setOuterRadius", double.class),
				"auto",
				Reflection.findMethod(Bulkhead.class, "setOuterRadiusAutomatic", boolean.class)));
		
		// CenteringRing
		setters.put("CenteringRing:innerradius", new DoubleSetter(
				Reflection.findMethod(CenteringRing.class, "setInnerRadius", double.class),
				"auto",
				Reflection.findMethod(CenteringRing.class, "setInnerRadiusAutomatic", boolean.class)));
		setters.put("CenteringRing:outerradius", new DoubleSetter(
				Reflection.findMethod(CenteringRing.class, "setOuterRadius", double.class),
				"auto",
				Reflection.findMethod(CenteringRing.class, "setOuterRadiusAutomatic", boolean.class)));
		
		// MassObject
		setters.put("MassObject:packedlength", new DoubleSetter(
				Reflection.findMethod(MassObject.class, "setLength", double.class)));
		setters.put("MassObject:packedradius", new DoubleSetter(
				Reflection.findMethod(MassObject.class, "setRadius", double.class),
				"auto", " ",
				Reflection.findMethod(MassObject.class, "setRadiusAutomatic", boolean.class)));
		setters.put("MassObject:radialposition", new DoubleSetter(
				Reflection.findMethod(MassObject.class, "setRadialPosition", double.class)));
		setters.put("MassObject:radialdirection", new DoubleSetter(
				Reflection.findMethod(MassObject.class, "setRadialDirection", double.class),
				Math.PI / 180.0));
		
		// MassComponent
		setters.put("MassComponent:mass", new DoubleSetter(
				Reflection.findMethod(MassComponent.class, "setComponentMass", double.class)));
		/*
		 * setters.put("MassComponent:masscomponenttype", new DoubleSetter(
		 * Reflection.findMethod(MassComponent.class, "setMassComponentType",
		 * double.class)));
		 */
		setters.put("MassComponent:masscomponenttype", new EnumSetter<>(
				Reflection.findMethod(MassComponent.class, "setMassComponentType",
						MassComponent.MassComponentType.class),
				MassComponent.MassComponentType.class));
		/*
		 * setters.put("Transition:shape", new EnumSetter<Transition.Shape>(
		 * Reflection.findMethod(Transition.class, "setShapeType",
		 * Transition.Shape.class),
		 * Transition.Shape.class));
		 */

		// ShockCord
		setters.put("ShockCord:cordlength", new DoubleSetter(
				Reflection.findMethod(ShockCord.class, "setCordLength", double.class)));
		setters.put("ShockCord:material", new MaterialSetter(
				Reflection.findMethod(ShockCord.class, "setMaterial", Material.class),
				Material.Type.LINE));
		
		// RecoveryDevice
		setters.put("RecoveryDevice:cd", new DoubleSetter(
				Reflection.findMethod(RecoveryDevice.class, "setCD", double.class),
				"auto",
				Reflection.findMethod(RecoveryDevice.class, "setCDAutomatic", boolean.class)));
		setters.put("RecoveryDevice:deployevent", new EnumSetter<>(
				Reflection.findMethod(RecoveryDevice.class, "getDeploymentConfigurations"),
				Reflection.findMethod(DeploymentConfiguration.class, "setDeployEvent", DeployEvent.class),
				DeployEvent.class));
		setters.put("RecoveryDevice:deployaltitude", new DoubleSetter(
				Reflection.findMethod(RecoveryDevice.class, "getDeploymentConfigurations"),
				Reflection.findMethod(DeploymentConfiguration.class, "setDeployAltitude", double.class)));
		setters.put("RecoveryDevice:deploydelay", new DoubleSetter(
				Reflection.findMethod(RecoveryDevice.class, "getDeploymentConfigurations"),
				Reflection.findMethod(DeploymentConfiguration.class, "setDeployDelay", double.class)));
		setters.put("RecoveryDevice:material", new MaterialSetter(
				Reflection.findMethod(RecoveryDevice.class, "setMaterial", Material.class),
				Material.Type.SURFACE));
		
		// Parachute
		setters.put("Parachute:diameter", new DoubleSetter(
				Reflection.findMethod(Parachute.class, "setDiameter", double.class)));
		setters.put("Parachute:linecount", new IntSetter(
				Reflection.findMethod(Parachute.class, "setLineCount", int.class)));
		setters.put("Parachute:linelength", new DoubleSetter(
				Reflection.findMethod(Parachute.class, "setLineLength", double.class)));
		setters.put("Parachute:linematerial", new MaterialSetter(
				Reflection.findMethod(Parachute.class, "setLineMaterial", Material.class),
				Material.Type.LINE));
		setters.put("Parachute:preset", new ComponentPresetSetter(
				Reflection.findMethod(Parachute.class, "loadPreset", ComponentPreset.class, Object[].class),
				false));

		// PodSet
		setters.put("PodSet:instancecount", new IntSetter(
				Reflection.findMethod(PodSet.class, "setInstanceCount", int.class)));
		setters.put("PodSet:radiusoffset", new RadiusPositionSetter());
		setters.put("PodSet:angleoffset", new AnglePositionSetter());

		// Streamer
		setters.put("Streamer:striplength", new DoubleSetter(
				Reflection.findMethod(Streamer.class, "setStripLength", double.class)));
		setters.put("Streamer:stripwidth", new DoubleSetter(
				Reflection.findMethod(Streamer.class, "setStripWidth", double.class)));
		
		// Rocket
		// <motorconfiguration> handled by separate handler
		setters.put("Rocket:referencetype", new EnumSetter<>(
				Reflection.findMethod(Rocket.class, "setReferenceType", ReferenceType.class),
				ReferenceType.class));
		setters.put("Rocket:customreference", new DoubleSetter(
				Reflection.findMethod(Rocket.class, "setCustomReferenceLength", double.class)));
		setters.put("Rocket:designer", new StringSetter(
				Reflection.findMethod(Rocket.class, "setDesigner", String.class)));
		setters.put("Rocket:revision", new StringSetter(
				Reflection.findMethod(Rocket.class, "setRevision", String.class)));
		setters.put("Rocket:designtype", new EnumSetter<>(
				Reflection.findMethod(Rocket.class, "setDesignType", DesignType.class),
				DesignType.class));
		setters.put("Rocket:kitname", new StringSetter(
				Reflection.findMethod(Rocket.class, "setKitName", String.class)));
//		setters.put("Rocket:optimizationflight", new BooleanSetter(
//				Reflection.findMethod(Rocket.class, "setOptimizationFlight", boolean.class)));
//		setters.put("Rocket:optimizationappearance", new BooleanSetter(
//				Reflection.findMethod(Rocket.class, "setOptimizationAppearance", boolean.class)));
//		setters.put("Rocket:optimizationconstruction", new BooleanSetter(
//				Reflection.findMethod(Rocket.class, "setOptimizationConstruction", boolean.class)));

		// Axial Stage
		setters.put("AxialStage:separationevent", new EnumSetter<>(
				Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
				Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationEvent",
						StageSeparationConfiguration.SeparationEvent.class),
				StageSeparationConfiguration.SeparationEvent.class));
		setters.put("AxialStage:separationaltitude", new DoubleSetter(
				Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
				Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationAltitude", double.class)));
		setters.put("AxialStage:separationdelay", new DoubleSetter(
				Reflection.findMethod(AxialStage.class, "getSeparationConfigurations"),
				Reflection.findMethod(StageSeparationConfiguration.class, "setSeparationDelay", double.class)));
	
		
	}
	
	
	/**
	 * Search for a enum value that has the corresponding name as an XML value.  The current
	 * conversion from enum name to XML value is to lowercase the name and strip out all 
	 * underscore characters.  This method returns a match to these criteria, or <code>null</code>
	 * if no such enum exists.
	 * 
	 * @param <T>			then enum type.
	 * @param name			the XML value, null ok.
	 * @param enumClass		the class of the enum.
	 * @return				the found enum value, or <code>null</code>.
	 */
	public static <T extends Enum<T>> Enum<T> findEnum(String name,
			Class<? extends Enum<T>> enumClass) {
		
		if (name == null)
			return null;
		name = name.trim();
		for (Enum<T> e : enumClass.getEnumConstants()) {
			if (e.name().toLowerCase(Locale.ENGLISH).replace("_", "").equals(name)) {
				return e;
			}
		}
		return null;
	}
	
	
	/**
	 * Convert a string to a double including formatting specifications of the OpenRocket
	 * file format.  This accepts all formatting that is valid for 
	 * <code>Double.parseDouble(s)</code> and a few others as well ("Inf", "-Inf").
	 * 
	 * @param s		the string to parse.
	 * @return		the numerical value.
	 * @throws NumberFormatException	the the string cannot be parsed.
	 */
	public static double stringToDouble(String s) throws NumberFormatException {
		if (s == null)
			throw new NumberFormatException("null string");
		if (s.equalsIgnoreCase("NaN"))
			return Double.NaN;
		if (s.equalsIgnoreCase("Inf"))
			return Double.POSITIVE_INFINITY;
		if (s.equalsIgnoreCase("-Inf"))
			return Double.NEGATIVE_INFINITY;
		return Double.parseDouble(s);
	}
}
